using System;
using System.Collections;
using System.IO;
using System.Text;

using Qiang.Topology.Geometries;
using Qiang.Topology.Algorithm;

namespace Qiang.Topology.IO
{
    /// <summary>
    ///  A class for reading shapefiles data.
    /// </summary>
    public class ShapeFileReader : ShapeReader
    {        
        /// <summary>
        /// Default empty constructor
        /// </summary>
        public ShapeFileReader() : base() { }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="factory"></param>
        public ShapeFileReader(GeometryFactory factory) : base(factory) {}

        /// <summary>
        /// Reads a generic stream containing geographic data saved as shapefile structure, 
        /// and returns a collection of all the features contained.
        /// Since NTS Geometry Model not support Z and M data, those informations are ignored if presents in shapefile.
        /// </summary>
        /// <param name="stream">Shapefile data stream.</param>
        /// <returns><c>GeometryCollection</c> containing all geometries in shapefile.</returns>
        public virtual IGeometryCollection Read(Stream stream)
        {
            using (BinaryReader reader = new BinaryReader(stream))
            {
                ArrayList list = null;                
                // Header file
                stream.Seek(32, SeekOrigin.Begin);
                int shapeType = reader.ReadInt32();         // ShapeTypes

                switch ((ShapeGeometryTypes)shapeType)
                {
                    case ShapeGeometryTypes.Point:
                    case ShapeGeometryTypes.PointZ:
                    case ShapeGeometryTypes.PointM:
                    case ShapeGeometryTypes.PointZM:
                        list = new ArrayList(ReadPointData(stream));
                        break;

                    case ShapeGeometryTypes.LineString:
                    case ShapeGeometryTypes.LineStringZ:
                    case ShapeGeometryTypes.LineStringM:
                    case ShapeGeometryTypes.LineStringZM:
                        list = new ArrayList(ReadLineStringData(stream));
                        break;

                    case ShapeGeometryTypes.Polygon:
                    case ShapeGeometryTypes.PolygonZ:
                    case ShapeGeometryTypes.PolygonM:
                    case ShapeGeometryTypes.PolygonZM:
                        list = new ArrayList(ReadPolygonData(stream));
                        break;

                    case ShapeGeometryTypes.MultiPoint:
                    case ShapeGeometryTypes.MultiPointZ:
                    case ShapeGeometryTypes.MultiPointM:
                    case ShapeGeometryTypes.MultiPointZM:
                        list = new ArrayList(ReadMultiPointData(stream));
                        break;

                    case ShapeGeometryTypes.MultiPatch:
                        throw new NotImplementedException("FeatureType " + shapeType + " not supported.");

                    default:
                        throw new ArgumentOutOfRangeException("FeatureType " + shapeType + " not recognized by the system");
                }
                IGeometryCollection collection = CreateGeometryCollection(list);                
                return collection;
            }
        }

        /// <summary>
        /// Reads a shapefile containing geographic data, 
        /// and returns a collection of all the features contained.
        /// Since NTS Geometry Model not support Z and M data, those informations are ignored if presents in shapefile.
        /// </summary>
        /// <param name="filepath">Shapefile path.</param>
        /// <returns><c>GeometryCollection</c> containing all geometries in shapefile.</returns>
        public virtual IGeometryCollection Read(string filepath)
        {
            using (Stream stream = new FileStream(filepath, FileMode.Open, FileAccess.Read,FileShare.Read))
            {
                return Read(stream);
            }            
        }                

        /// <summary>
        /// Reads Point shapefile
        /// </summary>
        /// <param name="stream"></param>
        protected virtual IList ReadPointData(Stream stream)
        {            
            IList list = new ArrayList();

            using (BinaryReader reader = new BinaryReader(stream))
            {                
                stream.Seek(100, SeekOrigin.Begin);              // Jump to first header                

                // For each header                
                while (stream.Position < stream.Length)
                {
                    stream.Seek(12, SeekOrigin.Current);        // Jump header and shapeType 

                    Coordinate coordinate = ReadCoordinate(reader);
                    IGeometry point = CreatePoint(coordinate);
                    list.Add(point);
                }
                return list;
            }            
        }        

        /// <summary>
        /// Reads LineString shapefile
        /// </summary>
        /// <param name="stream"></param>
        /// <returns></returns>
        protected virtual IList ReadLineStringData(Stream stream)
        {
            IList list = new ArrayList();

            using (BinaryReader reader = new BinaryReader(stream))
            {
                stream.Seek(100, SeekOrigin.Begin);              // Jump to first header                

                // For each header                
                while (stream.Position < stream.Length)
                {                                        
                    stream.Seek(12, SeekOrigin.Current);
                    ReadBoundingBox(reader);

                    int[] indexParts = null;
                    int numParts = ReadNumParts(reader);
                    int numPoints = ReadNumPoints(reader);

                    indexParts = ReadIndexParts(reader, numParts);
                    Coordinate[] coordinates = ReadCoordinates(reader, numPoints);                    

                    if (numParts == 1)   
                         list.Add(CreateLineString(coordinates));                                       
                    else list.Add(CreateMultiLineString(numPoints, indexParts, coordinates));
                }
                return list;
            }            
        }

        /// <summary>
        /// Reads Polygon shapefile
        /// </summary>
        /// <param name="stream"></param>
        /// <returns></returns>
        protected virtual IList ReadPolygonData(Stream stream)
        {
            IList list = new ArrayList();

            using (BinaryReader reader = new BinaryReader(stream))
            {
                stream.Seek(100, SeekOrigin.Begin);              // Jump to first header                

                // For each header                
                while (stream.Position < stream.Length)
                {
                    stream.Seek(12, SeekOrigin.Current);
                    ReadBoundingBox(reader);

                    int[] indexParts = null;
                    int numParts = ReadNumParts(reader);
                    int numPoints = ReadNumPoints(reader);

                    indexParts = ReadIndexParts(reader, numParts);
                    Coordinate[] coordinates = ReadCoordinates(reader, numPoints);

                    if (numParts == 1)
                        list.Add(CreateSimpleSinglePolygon(coordinates));
                    else list.Add(CreateSingleOrMultiPolygon(numPoints, indexParts, coordinates));
                }
                return list;
            }
        }

        /// <summary>
        /// Reads MultiPoint shapefile
        /// </summary>
        /// <param name="stream"></param>
        /// <returns></returns>
        protected virtual IList ReadMultiPointData(Stream stream)
        {
            IList list = new ArrayList();

            using (BinaryReader reader = new BinaryReader(stream))
            {
                stream.Seek(100, SeekOrigin.Begin);              // Jump to first header                

                // For each header                
                while (stream.Position < stream.Length)
                {                    
                    stream.Seek(12, SeekOrigin.Current);
                    ReadBoundingBox(reader);

                    int numPoints = ReadNumPoints(reader);
                    Coordinate[] coords = new Coordinate[numPoints];
                    for (int i = 0; i < numPoints; i++)
                        coords[i] = ReadCoordinate(reader);                    
                    list.Add(CreateMultiPoint(coords));
                }
                return list;
            }
        }        
    }
}
